package org.zetaframework.file;

import cn.hutool.core.lang.Assert;
import com.aliyun.oss.ClientBuilderConfiguration;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.auth.CredentialsProvider;
import com.aliyun.oss.common.auth.DefaultCredentialProvider;
import com.aliyun.oss.common.comm.Protocol;
import com.aliyun.oss.common.comm.SignVersion;
import io.minio.MinioClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.zetaframework.core.enums.FileStorageType;
import org.zetaframework.core.properties.FileProperties;
import org.zetaframework.file.strategy.FileContext;
import org.zetaframework.file.strategy.FileStrategy;

import java.util.Map;

/**
 * <h1>文件管理配置</h1>
 *
 * 说明：
 * <pre>
 * 参考<a href="https://github.com/zuihou/lamp-boot/tree/master/lamp-file">lamp-file</a>实现
 * </pre>
 * @author gcc
 */
@Configuration
@EnableConfigurationProperties(FileProperties.class)
public class FileConfiguration {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private final FileProperties fileProperties;
    private final Map<String, FileStrategy> strategyMap;
    public FileConfiguration(FileProperties fileProperties, Map<String, FileStrategy> strategyMap) {
        this.fileProperties = fileProperties;
        this.strategyMap = strategyMap;
    }


    /**
     * <p>配置使用策略的客户端</p>
     *
     * 说明：
     * <pre>
     * 和lamp-file动态策略方式不同。本项目根据项目配置文件中zeta.file.storageType的值来设置具体的策略。
     * 也就是说，不会像lamp-file那样根据用户传入的策略值来使用具体的策略上传文件。
     * 因为我认为一般的项目基本不会在多个OSS之间来回切换着上传文件，基本上都是选定一种OSS就一直用着。
     * </pre>
     *
     * 说说这种方式的缺点吧：
     * <pre>
     * 假如你项目之前使用的是阿里云，然后业务突然要求使用七牛云或者其它什么乱七八糟云
     * 虽然修改配置可以切换使用指定的上传策略，但是因为策略实现变成了七牛云，这会导致删除文件功能会受到一定的影响
     * 不能删除原来阿里云的文件了，所以开发需要对这种情况做一下预防。
     * 或者直接对删除文件接口做一下修改，像lamp-file那样可以删除不同oss的文件。
     * 当然你不在意文件是否删除也是可以的。数据库里面对应的数据是肯定会删除的
     * </pre>
     */
    @Bean
    public FileContext fileContext() {
        // 获取项目配置的策略
        FileStorageType strategyType = fileProperties.getStorageType();
        switch (strategyType) {
            case LOCAL -> logger.info("检测到oss策略为：LOCAL");
            case MINIO -> logger.info("检测到oss策略为：MINIO");
            case ALI_OSS -> logger.info("检测到oss策略为：ALI_OSS");
            default -> logger.info("检测到oss策略为: 自定义");
        }
        FileStrategy fileStrategy = strategyMap.get(strategyType.name());
        Assert.notNull(fileStrategy, "请配置正确的文件存储类型");
        return new FileContext(fileStrategy);
    }


    /**
     * 配置minio客户端
     * <p>
     * 说明：
     * 只有oss策略为MINIO的时候才配置
     */
    @Bean
    @ConditionalOnExpression(value = "#{'MINIO'.equals(environment['zeta.file.storageType'])}")
    public MinioClient minioClient() {
        FileProperties.Minio minio = fileProperties.getMinio();
        logger.info("正在初始化minio客户端...");
        Assert.notBlank(minio.getBucket(), "请配置Minio存储桶bucket");
        Assert.notBlank(minio.getEndpoint(), "请配置Minio访问域名endpoint");
        Assert.notBlank(minio.getAccessKey(), "请配置Minio用户名");
        Assert.notBlank(minio.getSecretKey(), "请配置Minio密码");

        return new MinioClient.Builder()
                .endpoint(minio.getEndpoint())
                .credentials(minio.getAccessKey(), minio.getSecretKey())
                .build();
    }

    /**
     * 配置阿里云 OSS 客户端
     * <p>
     * 说明：
     * 只有oss策略为ALI_OSS的时候才配置
     * <a href="https://help.aliyun.com/zh/oss/developer-reference/oss-java-sdk/">文档</a>
     */
    @Bean
    @ConditionalOnExpression(value = "#{'ALI_OSS'.equals(environment['zeta.file.storageType'])}")
    public OSS aliOssClient() {
        FileProperties.Ali aliOss = fileProperties.getAli();
        logger.info("正在初始化阿里云 OSS 客户端...");
        Assert.notBlank(aliOss.getBucket(), "请配置阿里云OSS 存储桶bucket");
        Assert.notBlank(aliOss.getEndpoint(), "请配置阿里云OSS 访问域名endpoint");
        Assert.notBlank(aliOss.getRegion(), "请配置阿里云OSS所属区域region");
        Assert.notBlank(aliOss.getAccessKeyId(), "请配置阿里云OSS AccessKeyId");
        Assert.notBlank(aliOss.getAccessKeySecret(), "请配置阿里云OSS AccessKeySecret");

        // 创建 ClientBuilderConfiguration 实例，用于配置 OSS 客户端参数
        ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
        // 设置签名算法版本为 V4
        clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
        // 设置使用 HTTPS 协议访问 OSS，保证传输安全性
        clientBuilderConfiguration.setProtocol(Protocol.HTTPS);
        // 设置最大错误重试次数，默认为3
        clientBuilderConfiguration.setMaxErrorRetry(5);

        // 创建凭证提供者实例
        CredentialsProvider credentialsProvider = new DefaultCredentialProvider(aliOss.getAccessKeyId(), aliOss.getAccessKeySecret());

        // 创建阿里云OSS客户端
        return OSSClientBuilder.create()
                .endpoint(aliOss.getEndpoint())
                .credentialsProvider(credentialsProvider)
                .clientConfiguration(clientBuilderConfiguration)
                .region(aliOss.getRegion())
                .build();
    }

}
